﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;

namespace WebApiExtend
{
    /// <summary>
    /// 设置器委托
    /// </summary>
    /// <param name="target"></param>
    /// <param name="arg"></param>
    public delegate void SetValueDelegate(object target, object arg);
    /// <summary>
    /// 访问器委托
    /// </summary>
    /// <param name="target">目标对象</param>
    /// <returns></returns>
    public delegate object GetValueDelegate(object target);

    public static class DynamicMethodFactory
    {
        /// <summary>
        /// 获取访问器
        /// </summary>
        /// <param name="property"></param>
        /// <returns></returns>
        public static GetValueDelegate GetGetter(this PropertyInfo property)
        {
            return DynamicMethodFactory.CreatePropertyGetter(property);
        }

        /// <summary>
        /// 获取设置器
        /// </summary>
        /// <param name="property"></param>
        /// <returns></returns>
        public static SetValueDelegate GetSetter(this PropertyInfo property)
        {
            return DynamicMethodFactory.CreatePropertySetter(property);
        }

        private static SetValueDelegate CreatePropertySetter(PropertyInfo property)
        {
            if (property == null)
            {
                throw new ArgumentNullException("property");
            }

            if (!property.CanWrite)
            {
                return null;
            }

            MethodInfo setMethod = property.GetSetMethod(true);

            DynamicMethod dm = new DynamicMethod("PropertySetter", null,
                new Type[] { typeof(object), typeof(object) }, property.DeclaringType, true);

            ILGenerator il = dm.GetILGenerator();

            if (!setMethod.IsStatic)
            {
                il.Emit(OpCodes.Ldarg_0);
            }
            il.Emit(OpCodes.Ldarg_1);

            EmitCastToReference(il, property.PropertyType);
            if (!setMethod.IsStatic && !property.DeclaringType.IsValueType)
            {
                il.EmitCall(OpCodes.Callvirt, setMethod, null);
            }
            else
            {
                il.EmitCall(OpCodes.Call, setMethod, null);
            }

            il.Emit(OpCodes.Ret);

            return (SetValueDelegate)dm.CreateDelegate(typeof(SetValueDelegate));
        }

        private static GetValueDelegate CreatePropertyGetter(PropertyInfo property)
        {
            if (property == null)
            {
                throw new ArgumentNullException("property");
            }

            if (!property.CanRead)
            {
                return null;
            }

            MethodInfo getMethod = property.GetGetMethod(true);

            DynamicMethod dm = new DynamicMethod("PropertyGetter", typeof(object), new[] { typeof(object) }, property.DeclaringType, true);

            Type returnType = getMethod.ReturnType;
            ILGenerator il = dm.GetILGenerator();


            il.Emit(OpCodes.Ldarg_0);
            EmitCastToReference(il, getMethod.DeclaringType);

            if (getMethod.IsFinal)
            {
                il.Emit(OpCodes.Call, getMethod);
            }
            else
            {
                il.Emit(OpCodes.Callvirt, getMethod);
            }

            if (returnType.IsValueType)
            {
                il.Emit(OpCodes.Box, returnType);
            }

            il.Emit(OpCodes.Ret);
            il.Emit(OpCodes.Ret);

            return (GetValueDelegate)dm.CreateDelegate(typeof(GetValueDelegate));
        }
        private static void EmitCastToReference(ILGenerator il, Type type)
        {
            if (type.IsValueType)
            {
                il.Emit(OpCodes.Unbox_Any, type);
            }
            else
            {
                il.Emit(OpCodes.Castclass, type);
            }
        }
    }
}
